/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    FlexibleDecimalFormat.java
 *    Copyright (C) 2002-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.core.matrix;

import java.text.DecimalFormat;
import java.text.FieldPosition;

/**
 * @author Yong Wang
 * @version $Revision$
 */
public class FlexibleDecimalFormat extends DecimalFormat {

    /** for serialization */
    private static final long serialVersionUID = 110912192794064140L;

    private DecimalFormat nf = null;
    private int digits = 7;
    private boolean exp = false;
    private int intDigits = 1;
    private int decimalDigits = 0;
    private int expDecimalDigits = 0; // ???
    private int power = 2;
    private boolean trailing = false;
    private boolean grouping = false;
    private boolean sign = false;

    public FlexibleDecimalFormat() {
        this(5);
    }

    public FlexibleDecimalFormat(int digits) {
        if (digits < 1) {
            throw new IllegalArgumentException("digits < 1");
        }
        this.digits = digits;
        intDigits = 1;
    }

    public FlexibleDecimalFormat(int digits, boolean trailing) {
        this(digits);
        this.trailing = trailing;
    }

    public FlexibleDecimalFormat(int digits, boolean exp, boolean trailing, boolean grouping) {
        this.trailing = trailing;
        this.exp = exp;
        this.digits = digits;
        this.grouping = grouping;
        if (exp) {
            this.intDigits = 1;
            this.decimalDigits = digits - intDigits;
        } else {
            this.intDigits = Math.max(1, digits - decimalDigits);
        }
    }

    public FlexibleDecimalFormat(double d) {
        newFormat(d);
    }

    private void newFormat(double d) {
        if (needExponentialFormat(d)) {
            exp = true;
            intDigits = 1;
            expDecimalDigits = decimalDigits(d, true);
            if (d < 0) {
                sign = true;
            } else {
                sign = false;
            }
        } else {
            exp = false;
            intDigits = Math.max(1, intDigits(d));
            decimalDigits = decimalDigits(d, false);
            if (d < 0.0) {
                sign = true;
            } else {
                sign = false;
            }
        }
    }

    public void update(double d) {
        if (Math.abs(intDigits(d) - 1) > 99) {
            power = 3;
        }
        expDecimalDigits = Math.max(expDecimalDigits, decimalDigits(d, true));
        if (d < 0) {
            sign = true;
        }
        if (needExponentialFormat(d) || exp) {
            exp = true;
        } else {
            intDigits = Math.max(intDigits, intDigits(d));
            decimalDigits = Math.max(decimalDigits, decimalDigits(d, false));
            if (d < 0) {
                sign = true;
            }
        }
    }

    private static int intDigits(double d) {
        return (int) Math.floor(Math.log(Math.abs(d * (1 + 1e-14))) / Math.log(10)) + 1;
    }

    private int decimalDigits(double d, boolean expo) {
        if (d == 0.0) {
            return 0;
        }
        d = Math.abs(d);
        int e = intDigits(d);
        if (expo) {
            d /= Math.pow(10, e - 1);
            e = 1;
        }
        if (e >= digits) {
            return 0;
        }
        int iD = Math.max(1, e);
        int dD = digits - e;
        if (!trailing && dD > 0) { // to get rid of trailing zeros
            FloatingPointFormat f = new FloatingPointFormat(iD + 1 + dD, dD, true);
            String dString = f.nf.format(d);
            while (dD > 0) {
                if (dString.charAt(iD + 1 + dD - 1) == '0') {
                    dD--;
                } else {
                    break;
                }
            }
        }
        return dD;
    }

    public boolean needExponentialFormat(double d) {
        if (d == 0.0) {
            return false;
        }
        int e = intDigits(d);
        if (e > digits + 5 || e < -3) {
            return true;
        } else {
            return false;
        }
    }

    public void grouping(boolean grouping) {
        this.grouping = grouping;
    }

    private void setFormat() {
        int dot = 1;
        if (decimalDigits == 0) {
            dot = 0;
        }
        if (exp) {
            nf = new ExponentialFormat(1 + expDecimalDigits, power, sign, grouping || trailing);
        } else {
            int s = sign ? 1 : 0;
            nf = new FloatingPointFormat(s + intDigits + dot + decimalDigits, decimalDigits, grouping || trailing);
        }
    }

    private void setFormat(double d) {
        newFormat(d);
        setFormat();
    }

    @Override
    public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
        if (grouping) {
            if (nf == null) {
                setFormat();
            }
        } else {
            setFormat(number);
        }

        return toAppendTo.append(nf.format(number));
    }

    public int width() {

        if (!trailing && !grouping) {
            throw new RuntimeException("flexible width");
        }

        return format(0.).length();
    }

    public StringBuffer formatString(String str) {
        int w = width();
        int h = (w - str.length()) / 2;
        StringBuffer text = new StringBuffer();
        for (int i = 0; i < h; i++) {
            text.append(' ');
        }
        text.append(str);
        for (int i = 0; i < w - h - str.length(); i++) {
            text.append(' ');
        }
        return text;
    }

}
